package be.ac.ua.auctioneer;

import java.io.IOException;
import java.rmi.RemoteException;
import java.util.UUID;
import java.util.Vector;

import distrcomp.Address;

import net.jini.core.entry.Entry;
import net.jini.core.entry.UnusableEntryException;
import net.jini.core.lease.LeaseDeniedException;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.transaction.Transaction;
import net.jini.core.transaction.TransactionException;
import net.jini.core.transaction.TransactionFactory;
import net.jini.core.transaction.server.TransactionManager;
import net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.lease.LeaseRenewalManager;
import net.jini.lookup.LookupCache;
import net.jini.lookup.ServiceDiscoveryEvent;
import net.jini.lookup.ServiceDiscoveryListener;
import net.jini.lookup.ServiceDiscoveryManager;
import net.jini.lookup.entry.Name;
import net.jini.space.JavaSpace;
import be.ac.ua.codebase.BankInterface;
import be.ac.ua.codebase.DutchAuction;
import be.ac.ua.codebase.EnglishAuction;
import be.ac.ua.codebase.ProductOffer;
import be.ac.ua.codebase.StoreConnectorInterface;
import be.ac.ua.codebase.VickreyAuction;

public class AuctioneerManager implements DiscoveryListener, ServiceDiscoveryListener {
	protected Vector<ServiceRegistrar> fRegistrars = new Vector<ServiceRegistrar>();
	private Vector<JavaSpace> spaces = new Vector<JavaSpace>();
	private StoreConnectorInterface fStoreConnector = null;
	private BankInterface bank = null;
	private UUID bankAccountID = null;
	private Address address = new Address("Middelheimlaan","1","38115","Antwerp","US");
	private String reg;
	
	public AuctioneerManager(String reg) throws IOException {
		//To discover services (and JavaSpaces)
		String[] groups = new String[] {"NDFLEXTRADE"};
		LookupDiscoveryManager lookupDiscovery = new LookupDiscoveryManager(groups, null, this);
        ServiceDiscoveryManager serviceDiscovery = new ServiceDiscoveryManager(lookupDiscovery, new LeaseRenewalManager());
		Class[] classes = new Class[] {JavaSpace.class};
        LookupCache cache = serviceDiscovery.createLookupCache(new ServiceTemplate(null, classes, null), null, this);
        
        this.reg = reg;
	}
	
	public UUID getBankAccountID() {
		return this.bankAccountID;
	}
	
	public Address getAddress() {
		return this.address;
	}
	
	public String getReg() {
		return this.reg;
	}
	
	public void registerWithBank() {
       	try {
       		if (this.bank == null) {
       			getBank();
       		}
       		this.bank = this.getBank();
			this.bankAccountID = this.bank.register("Auctioneer", "NDFLEXTRADE", "1000");
		} catch (Exception e) {
			System.out.println("[AUCTIONEERMANAGER] Failed to register with a bank");
			System.out.println(e.getMessage());
			e.printStackTrace();
		}
	}

	public BankInterface getBank() {
		if(this.bank == null) {
			BankInterface bankInterface = null; 
			Entry[] attributes = new Entry[] {new Name("Bank")}; 
			Class[] classes = new Class[] {BankInterface.class};
	
			for (int i = 0; i < fRegistrars.size(); i++) {
				try {
					BankInterface tmp = (BankInterface) fRegistrars.get(i).lookup(new ServiceTemplate(null, classes, null));

					if(tmp != null) {
						this.bank = tmp;
						break;
					}
				} catch(RemoteException e) {
					System.out.println("[AUCTIONEERMANAGER] Failed to get the bank");
				}
			}
		}
		return this.bank;
	}

	public void discarded(DiscoveryEvent evt) {
		ServiceRegistrar[] registrars = evt.getRegistrars();
		for (int n = 0; n < registrars.length; n++) { 
			fRegistrars.remove(registrars[n]);
		}		
	}

	public void discovered(DiscoveryEvent evt) { 
		ServiceRegistrar[] registrars = evt.getRegistrars();
		for (int n = 0; n < registrars.length; n++) { 
			fRegistrars.add(registrars[n]);
		}
	}

	public void serviceAdded(ServiceDiscoveryEvent discoveryEvent) {
		this.spaces.add((JavaSpace) discoveryEvent.getPostEventServiceItem().service);
		System.out.println("[AUCTIONEERMANAGER] Got new javaspace with ID " + discoveryEvent.getPostEventServiceItem().serviceID);
	}

	public void serviceChanged(ServiceDiscoveryEvent discoveryEvent) {
		ServiceItem item = discoveryEvent.getPreEventServiceItem();
		this.spaces.remove((JavaSpace) item.service);
		
		item = discoveryEvent.getPostEventServiceItem();
		this.spaces.add((JavaSpace) item.service);
	}

	public void serviceRemoved(ServiceDiscoveryEvent discoveryEvent) {
		ServiceItem item = discoveryEvent.getPreEventServiceItem();
		this.spaces.remove((JavaSpace) item.service);
	}
	
	public TransactionManager getTransactionManager() {
		TransactionManager transactionManager = null;
		
		Entry[] attributes = new Entry[1];
		attributes[0] = new Name ("Transaction Manager");
		Class[] classes = new Class[] {TransactionManager.class};
		
		for (int i = 0; i < fRegistrars.size(); i++) {
			try {
				TransactionManager tmp = (TransactionManager) fRegistrars.get(i).lookup(new ServiceTemplate(null, classes, null));
				if (tmp != null) {
					transactionManager = tmp;
				}
			} catch(RemoteException e) {
				System.out.println("[AUCTIONEERMANAGER] Failed to get the transaction manager");
			}
		}
		
		if (transactionManager == null) {
			System.out.println("[AUCTIONEERMANAGER] Failed to get the transaction manager");
		}
		
		return transactionManager;
	}
	
	public StoreConnectorInterface getStoreConnector() {
		if (this.fStoreConnector == null) {
			Entry[] attributes = new Entry[] {new Name("StoreConnector")};
			Class[] classes = new Class[] {StoreConnectorInterface.class};

			for (int i = 0; i < fRegistrars.size(); i++) {
				try {
					this.fStoreConnector = (StoreConnectorInterface) fRegistrars.get(i).lookup(new ServiceTemplate(null, classes, null));
					if (this.fStoreConnector != null) {
						break;
					}
				} catch(RemoteException e) {
					System.out.println("[AUCTIONEERMANAGER] Failed to get the store connector");
				}
			}
		}
		
		if (this.fStoreConnector == null) {
			System.out.println("[AUCTIONEERMANAGER] NO STORE CONNECTOR FOUND");
		}

		return this.fStoreConnector;
	}
	
	public Vector<ServiceRegistrar> getRegistars() {
		return this.fRegistrars;
	}
	
	public Vector<JavaSpace> getSpaces() {
		return this.spaces;
	}
	
	public void run() throws LeaseDeniedException, UnusableEntryException, TransactionException, InterruptedException, IOException {
		while(true) {
			if((this.spaces != null) && (this.spaces.size() > 0)) {
				//Iterate over JavaSpaces to look for new products
				
				//Initialize transaction
				TransactionManager transManager = this.getTransactionManager();
				if(transManager != null) {
					net.jini.core.transaction.Transaction.Created created = TransactionFactory.create(transManager, 5000);
					Transaction trans = created.transaction;
					
					//Initialize entries
					ProductOffer entryToMatch = new ProductOffer();
					ProductOffer gotEntry;
					
					//Find new products to auction
					for(int i=0; i<this.spaces.size(); ++i) {
						gotEntry = null;
						do {
							gotEntry = (ProductOffer) this.spaces.get(i).takeIfExists(entryToMatch, trans, 5000);
							if(gotEntry != null) {
								//New product to offer found, assign it to an auctioneer
								if(gotEntry.auctionType == VickreyAuction.class) {
									(new Thread(new VickreyAuctioneerNormalBilling(this, gotEntry))).start();
								} else if(gotEntry.auctionType == EnglishAuction.class) {
									(new Thread(new EnglishAuctioneerNormalBilling(this, gotEntry))).start();
								} else if(gotEntry.auctionType == DutchAuction.class) {
									(new Thread(new DutchAuctioneerNormalBilling(this, gotEntry))).start();
								}
							}
						} while(gotEntry != null);
					}
					
					//Commit transaction
					trans.commit();
				}
			}
			
			//Sleep
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
